﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using MarioArena.Multiplayer.TCP;
using MarioArena.World;

namespace MarioArena.Multiplayer
{
    class UDPReceiver
    {
        private const int Timeout = 5000; // The connection timeout in milliseconds

        private UdpClient mUdpClient;
        private IPEndPoint mEndPoint;

        private long mTimeLastReceived; // time of last received data in milliseconds
        private Thread mTimerThread;

        /// <summary>
        /// Used for checking if a received packet is actually newer than the previously received package.
        /// </summary>
        private int mReceivedPacketCounter;

        /// <summary>
        /// Constructs a new UDPReceiver.
        /// </summary>
        /// <param name="udpClient">UdpClient instance;</param>
        /// <param name="endPoint">An IPEndPoint containing the IP-address and port of the server.</param>
        public UDPReceiver(UdpClient udpClient, IPEndPoint endPoint)
        {
            this.mUdpClient = udpClient;
            this.mEndPoint = endPoint;

            this.mTimerThread = new Thread(new ThreadStart(TimeOut));
        }

        /// <summary>
        /// Starts receiving data using UDP.
        /// </summary>
        public void StartReceiving()
        {
            Console.WriteLine("Receiving...");
            try
            {
                ReceiveContinueousDataStream();
            }
            catch (Exception)
            {
                Console.WriteLine("ERROR WHILE RECEIVING CONNECTION CONFIRMATION");
            }
        }

        /// <summary>
        /// Starts receiving a contineous data stream using the UDP connection.
        /// </summary>
        public void ReceiveContinueousDataStream()
        {
            Console.WriteLine("receiving stream");
            byte[] receive_byte_array;
            try
            {
                while (UDPConnection.Instance.IsConnected)
                {
                    receive_byte_array = mUdpClient.Receive(ref mEndPoint);
                    ProcessIncomingData(receive_byte_array);
                    mTimeLastReceived = DateTime.Now.Ticks / 10000; // Current time in milliseconds
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("ERROR WHILE RECEIVING DATASTREAM: " + e);
            }
        }

        /// <summary>
        /// Processes the incoming data.
        /// </summary>
        /// <param name="receivedData">The data that has been received using the UDP connection.</param>
        private void ProcessIncomingData(byte[] receivedByteArray)
        {
            try
            {
                // Receive for each player: PlayerId#PacketCounter#CameraX_CameraY_SpeedX_SpeedY_Direction_LeftPressed_RightPressed_JumpPressed:
                string receivedData = Encoding.ASCII.GetString(receivedByteArray, 0, receivedByteArray.Length).Trim();

                string[] packetCounterSplit = receivedData.Split('@');
                int packetCounter = Convert.ToInt32(packetCounterSplit[0]);
                // Only process the packet if it's newer than the previous packet
                if (packetCounter > this.mReceivedPacketCounter)
                {
                    this.mReceivedPacketCounter = packetCounter;

                    string actualData = packetCounterSplit[1];

                    // Retrieve all different players
                    string[] playerStrings = actualData.Split(':');
                    for (int i = 0; i < playerStrings.Length; i++) // Length - 1 because the last element is always empty when splitting on ':'
                    {
                        try
                        {
                            // Retrieve all data from this player
                            string[] splitPlayerData = playerStrings[i].Split('#');

                            int id = Convert.ToInt32(splitPlayerData[0]); // Player Id

                            string[] playerData = splitPlayerData[1].Split('_'); // Player data
                            if (playerData.Length == 8) // Check if the data is sent correctly
                            {
                                // The server doesn't use this client's screen size for calculations. Therefore we need to 
                                // subtract half the screen size to basically center the projectile for the camera again.
                                int cameraX = Convert.ToInt32(playerData[0]) - (GameManager.GraphicsDevice.Viewport.Width / 2);
                                int cameraY = Convert.ToInt32(playerData[1]) - (GameManager.GraphicsDevice.Viewport.Height / 2);
                                float speedX = float.Parse(playerData[2]);
                                float speedY = float.Parse(playerData[3]);
                                int direction = Convert.ToInt32(playerData[4]);
                                string leftPressedString = playerData[5];
                                string rightPressedString = playerData[6];
                                string jumpPressedString = playerData[7];

                                int opponentCount = GameManager.Instance.Opponents.Count;
                                for (int j = 0; j < opponentCount; j++)
                                {
                                    Opponent currentOpponent = GameManager.Instance.Opponents[j];
                                    if (currentOpponent.Id.Equals(id))
                                    {
                                        // Store the data in the opponent with the matching ID
                                        if (leftPressedString.Equals("1"))
                                            currentOpponent.LeftKeyPressed = true;
                                        else currentOpponent.LeftKeyPressed = false;
                                        if (rightPressedString.Equals("1"))
                                            currentOpponent.RightKeyPressed = true;
                                        else currentOpponent.RightKeyPressed = false;
                                        if (jumpPressedString.Equals("1"))
                                            currentOpponent.JumpKeyPressed = true;
                                        else currentOpponent.JumpKeyPressed = false;

                                        currentOpponent.Character.CameraX = cameraX;

                                        // Only update the Y position if not jumping, because the Y-speed 
                                        // is always in synch when jumping.
                                        if (speedY == 0)
                                            currentOpponent.Character.CameraY = cameraY;
                                        currentOpponent.Character.SpeedX = speedX;
                                        currentOpponent.Character.SpeedY = speedY;
                                        currentOpponent.Character.Direction = direction;
                                    }
                                }
                            }

                        }
                        catch (Exception)
                        {
                            Console.WriteLine("Could not process received UDP data properly.");
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Could not process incoming data properly. Exception: " + e);
            }
        }

        /// <summary>
        /// Times out if the user hasn't received any data for too long.
        /// </summary>
        private void TimeOut()
        {
            while (UDPConnection.Instance.IsConnected)
            {
                Thread.Sleep(5);
                if (mTimeLastReceived > 0)
                {
                    long currentTime = DateTime.Now.Ticks / 10000;
                    if (currentTime - mTimeLastReceived >= Timeout)
                    {
                        // Connection has timed out.
                        UDPConnection.Instance.Disconnect();
                        TCPConnection.Instance.Disconnect();

                        return; // Finish thread
                    }
                }
            }
        }
    }
}
